/*
	MIDAS Context Based Routing
	cbr.h
	Copyright (c) 2007 Radoslaw Olgierd Schoeneich
	Copyright (c) 2007 Bartlomiej Zolnierkiewicz
	MEAG IT PW
 */

#ifndef cbr_h_
#define cbr_h_

#include <sys/types.h>
#include <set>

#include "cbr_config.h"
#include "cbr_context_model.h"
#include "cbr_rtable.h"
#include "cbr_rreqtable.h"
#include "cbr_crtable.h"
#include "cbr_msgcache.h"

class CBR;

#include "cbr_acache.h" // needs class CBR

/**
 * Timers (hello, message cache, routing table).
 */

class cbr_HelloTimer : public Handler, private cbr_Object {
private:
	CBR	*agent;
	Event	intr;

public:
	cbr_HelloTimer(CBR *a) : agent(a) { }
	void handle(Event*);
};

class cbr_MsgCacheTimer : public Handler, private cbr_Object {
private:
	CBR	*agent;
	Event	intr;

public:
	cbr_MsgCacheTimer(CBR *a) : agent(a) { }
	void handle(Event*);
};

class cbr_CRTableTimer : public Handler, private cbr_Object {
private:
	CBR	*agent;
	Event	intr;

public:
	cbr_CRTableTimer(CBR *a) : agent(a) { }
	void handle(Event*);
};

class cbr_RecvTimer : public Handler, private cbr_Object {
private:
	CBR	*agent;
	Event	intr;

public:
	cbr_RecvTimer(CBR *a) : agent(a) { }
	void handle(Event*);
};

class cbr_CentralNode {
private:
	cbr_ID		id;

	// communications with Central Node is always done using IP
	ip_addr		ip;
	mac_addr	mac; // for testing purposes
public:
	bool		parse(std::istream &input);
	void		banner();

	cbr_ID		getID() const { return id; }
	ip_addr		getIP() { return ip; }
	mac_addr	getMAC() { return mac; }
};

class cbr_NetworkInterface {
private:
	std::string	ifname;
	bool		is_managed;
	int		socket;
	int		index;
	ip_addr		ip;
public:
	cbr_NetworkInterface(const char *name, bool managed, int sock, int i, ip_addr ipa)
	{
		ifname.append(name);
		is_managed = managed;
		socket = sock;
		index = i;
		ip = ipa;
	}
	~cbr_NetworkInterface()
	{
		ifname.clear();
	}

	const char *	getName() { return ifname.c_str(); }
	bool		isManaged() { return is_managed; }
	int		getSocket() { return socket; }
	int		getIndex() { return index; }
	ip_addr getIpAddress() { return ip; }
};

typedef std::list<cbr_NetworkInterface *> interfaces_list;

class cbr_NetInterfaces {
private:
	interfaces_list interfaces;

	bool is_if_managed(const char *, bool &);

	bool get_if_hw_addr(const char *, mac_addr *);

	bool get_if_ip_addr(const char *, ip_addr *);

public:
	cbr_NetInterfaces() { };
	~cbr_NetInterfaces()
	{
		while (!interfaces.empty()) {
			delete interfaces.front();
			interfaces.pop_front();
		}
	}

	bool parse(std::istream &input);

	/**
	 * @todo
	 * - add remove()
	 */
	bool add(const char *, const char *, bool, bool);

	void add_to_mapping_caches(CBR *);

	void send(CBR *, char *);
	void recv(CBR *);
};

class cbr_NIS {
public:
	bool		parse(std::istream &, cbr_IPCache *);
};

/**
 * The routing agent.
 */

class CBR : private cbr_Object {
private:

	// HELLO compressor
	cbr_Compressor		compr;

	// context model
	cbr_ContextModel	model;

	// routing tables
	cbr_RReqTable		reqtable;
	cbr_RTable		rtable;

	// context routing table
	cbr_CRTable		crtable;

	// queue of outgoing packets (waiting for RREP)
	cbr_MsgCache		outgoing;
	// queue of incoming packets
	cbr_MsgCache		incoming;

	// local address
	cbr_ID			localID;
	cbr_ContextAddress*	localAddr;

	// flag set if the node operates in the "firewalled" mode
	bool			is_firewalled;

	// flag set if the node is the Central Node
	bool			is_central_node;

protected:
	int off_cbr_;

public:
	CBR(cbr_ID, cbr_ContextAddress *, const char *, std::string,
		const char *, std::string, bool, bool);
	~CBR();

	void setLocalCBA(cbr_ContextAddress* addr);

	// timers
	cbr_MsgCacheTimer	mtimer;
	cbr_CRTableTimer	crtimer;

	cbr_CentralNode		cn;

	cbr_NetInterfaces	netifs;

	// IP addresses on _all_ interfaces are always unique
	cbr_IPCache		ipcache;

	// this works fine as long as MACs on _all_ interfaces are unique,
	// if it ever turns out to be a problem this should be moved to
	// cbr_NetworkInterface (=> one MAC cache per one interface)
	cbr_MACCache		maccache;

	cbr_NIS			nis;

	void cbr_prep_send(char *, Packet *);
	void cbr_send(cbr_NetworkInterface *, char *);
	Packet *cbr_post_recv(char *);
	void cbr_recv(char *);

	cbr_ID getLocalID() const { return localID; }

	bool IsFirewalled() { return is_firewalled; }

	bool IsCentralNode() { return is_central_node; }

	// next transaction ID
	u_int32_t last_trID;

	u_int32_t getTransactionID();

	void recv(char *, Handler *);

	void purgeCRTable() { crtable.purge(); }
	void purgeMsgCache()
	{
		incoming.purge();
		outgoing.purge();
	}

	void sendHello();

	void linkFailureHandler(Packet* pkt);

	void sendDataMessage(cbr_Msg* m, double delay);

protected:
        // packet TX routines
	void forward(cbr_ID next_hop, Packet* pkt, double delay);
	void forward_rreq(const cbr_ContextAddress *, cbr_RReqPacket *, double);
	void forward_rreq_with_restrictions(const cbr_ContextAddress *,
					    cbr_RReqPacket *, double);

	std::set<cbr_ID>* getSourcesAll(const cbr_CRTable &crtable,
					cbr_Concept c);

	void sendPacket(cbr_Packet* pkt, cbr_ID fwdr, double delay = 0.0);
	void sendReply(cbr_RReqPacket* p, cbr_ID fwdr);

        // packet RX routines
        void recvHello(Packet *pkt);
        void recvRequest(Packet *, cbr_RReqPacket *,  double);
        void recvReply(Packet *pkt);
        void recvData(Packet *pkt);
};

static void inline convertstrtomac(const char *str, mac_addr *mac)
{
	int len = strlen(str);
	char *p = new char[len + 1], *s = p;
	strncpy(s, str, len);
	s[len] = '\0';

	for (int i = 0; i < 6; i++) {
		// ':' -> '\0'
		s[2] = '\0';
		int octet = strtoul(s, NULL, 16);
		mac->ether_addr_octet[i] = octet;
		s += 3; // remember about ':'
	}

	delete[] p;
}

static void inline dumpmac(mac_addr *mac)
{
	cbr_log("%02x:%02x:%02x:%02x:%02x:%02x\n",
		mac->ether_addr_octet[0],
		mac->ether_addr_octet[1],
		mac->ether_addr_octet[2],
		mac->ether_addr_octet[3],
		mac->ether_addr_octet[4],
		mac->ether_addr_octet[5]);
}

#endif	// cbr_h_
